home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Software Vault: The Gold Collection
/
Software Vault - The Gold Collection (American Databankers) (1993).ISO
/
cdr11
/
pd0836.zip
/
MAIN.C
< prev
next >
Wrap
C/C++ Source or Header
|
1993-04-30
|
66KB
|
2,020 lines
/*** MAIN.C - DSDUMP/DSSNAP main program
*
* DSDUMP: Display DoubleSpace Compressed Volume File (CVF) information.
* DSSNAP: Copy CVF system area to a file.
*
* Version 1.00.58 12-Mar-1993
*
* Notes:
* -DSNAP => builds DSSNAP program;
* if not defined, builds DSDUMP program.
*/
#include <ctype.h>
#include <dos.h> // get thin DOS INT 21h call interface
#include <fcntl.h>
#include <io.h>
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cvf.h" // Get CVF format
#include "drvinfo.h" // IsDoubleSpaceDrive()
//*****************************************************************************
//* CONSTANTS *
//*****************************************************************************
#define szProduct "DoubleSpace" // product name
#define verMAJOR 0 // Major version number (N.xxx)
#define verMINOR 58 // Minor version number (x.NN)
#define cbPERLINE 16 // Number of hex bytes per output line
#define cCOLUMNS 2 // Number of columns of hex output per line
#define cbFILEBUFFER (cbPERLINE*128) // Size of file buffer
#define cbOUTPUTLINEMAX 100 // Maximum line length on stdout
#define FR_CURRENT_POS -1 // FileRead iseek value to read from current
// position in the file.
#define chNOTPRINTABLE '.' // Used for non-printable characters in HEX out
#define chSWITCH '/' // Command line switch character
#define chSWITCH2 '-' // Alternate Command line switch character
#define chRANGE_START_END '-' // Start,End range separator (/M2-37)
#define chRANGE_START_COUNT '+' // Start,Length range separator (/C20+3)
#define cbDIR_ENT 32 // Number bytes per DOS directory entry
#define szCVF_ROOT "DBLSPACE" // Base name of CVF file
#define seqBAD 255 // Invalid CVF sequence number
#define seqMAX 254 // Largest valid CVF sequence number
#define cchMAXFILEPATH 129 // Maximum length of a file path
//*****************************************************************************
//* TYPES *
//*****************************************************************************
typedef int FILEHANDLE; /* fh */ // File handle
typedef int RETCODE; /* rc */ // Return code
typedef int SEQ; /* seq */ // CVF sequence number
typedef unsigned int UINT; /* ui */ // unsigned int
/*** RANGE - Range of a regions specified on command line
*
* dwRANGE_BAD - value for iFirst or iLast that indicates an error
* dwRANGE_DEFAULT - value for iFirst or iLast that indicates no
* command line value was supplied.
*/
typedef struct RANGE_t { /* range */
DWORD iFirst; // First entry to display
DWORD iLast; // Last entry to display
} RANGE;
typedef RANGE *PRANGE; /* prange */
#define dwRANGE_BAD 0xFFFFFFFF // Bad range value
#define dwRANGE_DEFAULT 0xFFFFFFFE // Default range value
/*** REGION - file region characterization
*
* Used to record data on the BitFAT, MDFAT, boot sector, DOS FAT,
* DOS root directory, and the sector heap.
*
* The cbTotal is the reserved amount of space in the file for the
* region. This is usually a limiting factor on growing the CVF.
*
* The cbActive is the length of the region (starting from the
* front) that is valid for the CVF at its current size.
*/
typedef struct { /* reg */
long ibStart; // Byte offset in file of region start
long cbTotal; // Total length of region in bytes
long cbActive; // Active area of region, in bytes
BOOL fDisplay; // TRUE => region is interesting
char *pszName; // Name of region
} REGION;
typedef REGION *PREGION; /* preg */
/*** INDEXREGION - index into areg of file regions
*
* WARNING: This enumeration must be kept parallel to areg!
*/
typedef enum { /* ireg */
iregMDBPB,
iregBITFAT,
iregRESERVED1,
iregMDFAT,
iregRESERVED2,
iregDOSBOOT,
iregRESERVED3,
iregDOSFAT,
iregDOSROOTDIR,
iregRESERVED4,
iregSECTORHEAP,
cregMAX, // Count of ireg's
} INDEXREGION;
/*** MEMBERTYPE - Display type for a structure member
*
* Used to describe the display format of a structure member.
*
* WARNING: The elements of this enumeration must match the array
* cbFromMT!
*/
typedef enum { /* mt */
mtBYTE, // printf("%02x",(WORD)BYTE);
mtBYTE3, // printf("%02x %02x %02x",(WORD)ab[0],(WORD)ab[1],(WORD)ab[2]);
mtCHAR, // printf("%c",char);
mtCHAR8, // printf("%8s",ach[8]);
mtDWORD, // printf("%08lx",DWORD);
mtINT1, // printf("%3d",(short)char);
mtINT2, // printf("%5d",short);
mtINT4, // printf("%10ld",long);
mtUINT1, // printf("%3u",(WORD)BYTE);
mtUINT2, // printf("%5u",WORD);
mtUINT4, // printf("%10lu",DWORD);
mtWORD, // printf("%04x",WORD);
} MEMBERTYPE;
#define mtHIDE 0x80 // OR with other mtXXXX => do not display
/*** MEMBERINFO - Describes a stucture member for formatting purposes
*
*/
typedef struct { /* mi */
MEMBERTYPE mt;
char *psz; // description
} MEMBERINFO;
typedef MEMBERINFO *PMEMBERINFO; /* pmi */
/*** GLOBAL - structure for global variables
*
* This is to make them obvious in the sources
*
*/
typedef struct {
int argc; // argc parameter passed to main(...)
char **argv; // argv parameter passed to main(...)
long cbCVF; // Size of CVF in bytes
long ibCVFStamp2; // Position of 2nd MD_STAMP in CVF
char chDrive; // Drive letter of mounted CVF, else 0
MDBPB mp; // MDBPB
BOOL fIgnoreSigCheck; // TRUE => Ignore signature check
#ifdef SNAP
#else
RANGE rangeBitFAT; // Display range for BitFAT
RANGE rangeBitFATValid; // Valid range for BitFAT
RANGE rangeCVF; // Display range for CVF sectors
RANGE rangeCVFValid; // Valid range for CVF sectors
RANGE rangeMDFAT; // Display range for MDFAT
RANGE rangeMDFATValid; // Valid MDFAT range
RANGE rangeHeap; // Display range for Sector Heap
RANGE rangeHeapValid; // Valid range for Sector Heap
BOOL fShowAddresses; // TRUE => -a specified on command line
BOOL fShowBitFAT; // TRUE => -b specified on command line
BOOL fShowCVFSectors; // TRUE => -c specified on command line
BOOL fShowDOSBoot; // TRUE => -t specified on command line
BOOL fShowDOSFAT; // TRUE => -f specified on command line
BOOL fShowDOSRootDir; // TRUE => -r specified on command line
BOOL fShowFragmentation; // TRUE => -g specified on command line
BOOL fShowHeader; // TRUE => -h specified on command line
BOOL fShowHeap; // TRUE => -s specified on command line
BOOL fShowMDFAT; // TRUE => -m specified on command line
BOOL fShowVerbose; // TRUE => -v specified on command line
BYTE ab[cbFILEBUFFER]; // File buffer
#endif
char ach[cbOUTPUTLINEMAX]; // Line output buffer
char achCVFName[cchMAXFILEPATH]; // CVF file name
#ifdef SNAP
char achOutFileName[cchMAXFILEPATH]; // snap output file name
#endif
} GLOBAL;
//*****************************************************************************
//* VARIABLES *
//*****************************************************************************
/*** g - Global variables
*
*/
GLOBAL g;
#ifndef SNAP // -----------------------------------------------------
/*** cbFromMt - Get size of structure member from MEMBERTYPE
*
* WARNING: The elements of this array must exactly parallel the
* MEMBERTYPE enumeration!
*/
int cbFromMt[] = {
1, // mtBYTE
3, // mtBYTE3
1, // mtCHAR
8, // mtCHAR8
4, // mtDWORD
1, // mtINT1
2, // mtINT2
4, // mtINT4
1, // mtUINT1
2, // mtUINT2
4, // mtUINT4
2, // mtWORD
};
/*** amiMDBPB - Description of MDBPB structure members
*
*/
MEMBERINFO amiMDBPB[] = {
{ mtBYTE3 , "jmpBOOT : Jump to bootstrap routine" },
{ mtCHAR8 , "achOEMName[8] : OEM Name" },
{ mtUINT2 , "cbPerSec : Count of bytes per sector" },
{ mtUINT1 , "csecPerClu : Count of sectors per cluster" },
{ mtUINT2 , "csecReserved : Count of reserved sectors" },
{ mtUINT1 , "cFATs : Count of FATs" },
{ mtUINT2 , "cRootDirEntries : Count of root directory entries" },
{ mtUINT2 , "csecTotalWORD : Count of total sectors" },
{ mtBYTE , "bMedia : Media byte" },
{ mtUINT2 , "csecFAT : Count of sectors occupied by the FAT" },
{ mtUINT2 , "csecPerTrack : Count of sectors per track" },
{ mtUINT2 , "cHeads : Count of heads" },
{ mtUINT4 , "csecHidden : Count of hidden sectors" },
{ mtUINT4 , "csecTotalDWORD : Count of total sectors" },
{ mtUINT2 , "secMDFATStart : Logical sector of MDFAT" },
{ mtUINT1 , "nLog2cbPerSec : Log base 2 of cbPerSec" },
{ mtUINT2 , "csecMDReserved : Number of sectors reserved by MD" },
{ mtUINT2 , "secRootDirStart : Logical sector of root directory" },
{ mtUINT2 , "secHeapStart : Logical sector of sector heap" },
{ mtUINT2 , "cluFirstData : DOS/DBLSPACE cluster offset" },
{ mtUINT1 , "cpageBitFAT : Count of 'pages' in the BitFAT" },
{ mtWORD |mtHIDE, "RESERVED1 : Reserved1" },
{ mtUINT1 , "nLog2csecPerClu : Log base 2 of csecPerClu" },
{ mtWORD |mtHIDE, "RESERVED2 : Reserved2" },
{ mtUINT4|mtHIDE, "RESERVED3 : Reserved3" },
{ mtUINT4|mtHIDE, "RESERVED4 : Reserved4" },
{ mtBYTE , "f12BitFAT : 1 => 12-bit FAT, 0 => 16-bit FAT" },
{ mtUINT2 , "cmbCVFMax : Maximum CVF size, in megabytes" },
};
#endif // ifndef SNAP -------------------------------------------------
/*** areg - CVF regions
*
* This array is filled in by reading the MDBPB and performing
* computations with its members.
*
* WARNING: This array must be kept parallel to INDEXREGION!
*/
REGION areg[] = {
/* iStart, cbTotal, cbActive, fInteresting, pszName */
/* 1234567890123456 */
{ 0L, 0L, 0L, 1, "MDBPB" },
{ 0L, 0L, 0L, 1, "BitFAT" },
{ 0L, 0L, 0L, 0, "<reserved1>" },
{ 0L, 0L, 0L, 1, "MDFAT" },
{ 0L, 0L, 0L, 0, "<reserved2>" },
{ 0L, 0L, 0L, 1, "Boot Sector" },
{ 0L, 0L, 0L, 0, "<reserved3>" },
{ 0L, 0L, 0L, 1, "DOS FAT" },
{ 0L, 0L, 0L, 1, "Root Directory" },
{ 0L, 0L, 0L, 0, "<reserved4>" },
{ 0L, 0L, 0L, 1, "Sector Heap" },
};
/*** szFromMDFAT - Map MDFAT flag bits to strings for display
*
*/
char *szFromMDFAT[] = {
"F,C", // free, compressed
"F,U", // free, uncompressed
"A,C", // allocated, compressed
"A,U" // allocated, uncompressed
};
/*** apszSyntax - Syntax help
*
*/
#define isynSUMMARY 2 // Index of syntax summary line
char *apszSyntax[] = {
#ifdef SNAP
"Takes a snapshot of the system area of a DoubleSpace drive.",
"",
"DSSNAP [/I] [drive] [/O=file]", // Must be isynSUMMARY'nd line
"",
" drive DoubleSpace drive, default is current drive;",
" An explicit CVF name may also be specified.",
" /I Ignore DoubleSpace signature check",
" /O=file File name of snapshot file; default is SNAPSHOT.DS",
#else // SNAP
"Displays formatted DoubleSpace drive information.",
"",
"DSDUMP [/AFHIRTV] [/BCMS[range]] [drive]", // Must be isynSUMMARY'nd line
"",
" drive DoubleSpace drive or CVF; default is current drive.",
" /A Display Addresses of file regions",
" /F Display DOS FAT",
" /G Display BitFAT fragmentation report",
" /H Display header",
" /I Ignore DoubleSpace signature check",
" /R Display DOS root directory",
" /T Display DOS boot sector",
" /V Display everything (verbose)",
" /B Display BitFAT",
" /C Display CVF sectors",
" /M Display MDFAT",
" /S Display Sector Heap",
" range An optional range of entries to display. Use 'n' to select entry n,",
" 'n-m' to select entries n through m, and 'n+c' to select c entries",
" starting at entry n. If omitted, all entries are displayed.",
" Examples: /M27, /S137-142, /C34+4",
#endif // SNAP
};
#ifdef SNAP
char szOutName[] = "SNAPSHOT.DS"; // default out file name
#endif
//*****************************************************************************
//* FUNCTION PROTOTYPES *
//*****************************************************************************
#ifdef SNAP
void SnapCVF(FILEHANDLE fh);
#else
void CheckRange(PRANGE prangeA, PRANGE prangeB, char *psz);
void DumpBitFAT(FILEHANDLE fh);
void DumpCVFSectors(FILEHANDLE fh);
void DumpDOSBoot(FILEHANDLE fh);
void DumpDOSFAT(FILEHANDLE fh);
void DumpDOSRootDir(FILEHANDLE fh);
void DumpMDBPB(void);
void DumpMDFAT(FILEHANDLE fh);
void DumpHex(FILEHANDLE fh, long iseek, long cb);
void DumpHexLine(long iseek, BYTE *pb);
void DumpHeap(FILEHANDLE fh);
void DumpSummary(BOOL fFull);
void FormatMember(char *pch,void *pv, MEMBERTYPE mt);
char * FormatPercent(char *psz,DWORD num,DWORD den);
BOOL GetBitFATBit(FILEHANDLE fh, DWORD i);
void PrintHeader(char *psz);
void PrintHeader2(char *psz1, char *psz2);
#endif // ifndef SNAP
BOOL BuildCVFName(char *psz, char ach[], int cb, char *chDrive);
void CheckSignature(FILEHANDLE fh, long seekpos, char *pszName);
void ComputeRegions(FILEHANDLE fh);
void Error(char *pszMsg, char *pszParm);
RETCODE FileRead(FILEHANDLE fh, long iseek, void *pb, WORD cb);
BOOL IsDriveSpec(char *psz);
int cdecl main(int argc, char **argv);
void ParseArgs(int argc, char **argv);
DWORD ParseDecimal(char **ppch);
void ParseRange(PRANGE prange, char **ppch);
void PrintBanner(void);
void ShowSyntax(BOOL fFull);
//*****************************************************************************
//* FUNCTIONS *
//*****************************************************************************
/*** main - entry point
*
*/
int cdecl main(int argc, char **argv)
{
FILEHANDLE fh;
RETCODE rc;
// Announce ourselves
PrintBanner();
#ifdef SNAP
strcpy(g.achOutFileName,szOutName); // Set default output file name
#endif
// Get arguments
ParseArgs(argc,argv); // If error, it exits
// If CVF is mounted, make sure it is fully up-to-date
if (g.chDrive)
FlushDrive(g.chDrive - 'A', FDOP_DISK_RESET);
// Open CVF
rc = _dos_open(g.achCVFName,O_RDONLY,&fh);
if (rc)
Error("Could not open %s",g.achCVFName);
// Read MDBPB from file
if (FileRead(fh,0L,&g.mp,sizeof(g.mp))) {
Error("Could not read MDBPB",NULL);
}
// Compute CVF Regions
ComputeRegions(fh);
#ifdef SNAP
SnapCVF(fh);
#else // SNAP
// Check ranges for validity, if specified
if (g.fShowBitFAT || g.fShowFragmentation)
CheckRange(&g.rangeBitFAT,&g.rangeBitFATValid,"BitFAT bit");
if (g.fShowCVFSectors)
CheckRange(&g.rangeCVF,&g.rangeCVFValid,"CVF sector");
if (g.fShowHeap)
CheckRange(&g.rangeHeap,&g.rangeHeapValid,"Heap sector");
if (g.fShowMDFAT)
CheckRange(&g.rangeMDFAT,&g.rangeMDFATValid,"MDFAT entry");
// Do operations indicated by flags
DumpSummary(g.fShowAddresses);
if (g.fShowHeader)
DumpMDBPB();
if (g.fShowBitFAT || g.fShowFragmentation)
DumpBitFAT(fh);
if (g.fShowCVFSectors)
DumpCVFSectors(fh);
if (g.fShowMDFAT)
DumpMDFAT(fh);
if (g.fShowDOSBoot)
DumpDOSBoot(fh);
if (g.fShowDOSFAT)
DumpDOSFAT(fh);
if (g.fShowDOSRootDir)
DumpDOSRootDir(fh);
if (g.fShowHeap)
DumpHeap(fh);
#endif // ifdef SNAP
// Close CVF
_dos_close(fh);
// Done
return 0;
}
#ifndef SNAP // ------------------------------------------------------
/*** CheckRange - Check that one range is contained in another
*
* Check range for inclusion; set default values, if indicated.
*
* Entry
* prangeA - range to be checked for inclusion
* prangeB - range that should include rangeA
* psz - String for error message
*
* Exit-Success
* prangeA is included in prangeB;
* *prangeA is updated to replace any dwRANGE_DEFAULT values
* with the corresponding values from prangeB.
*
* Exit-Failure
* prangeA is not completely inside prangeB.
* Error message printed, and program exits.
*/
void CheckRange(PRANGE prangeA, PRANGE prangeB, char *psz)
{
char ach[cbOUTPUTLINEMAX];
// Change default values to the bounding range
if (prangeA->iFirst == dwRANGE_DEFAULT)
prangeA->iFirst = prangeB->iFirst;
if (prangeA->iLast == dwRANGE_DEFAULT)
prangeA->iLast = prangeB->iLast;
// Check for inclusion
if (prangeA->iFirst < prangeB->iFirst) {
sprintf(ach,"%s %ld is not in valid range %ld..%ld",
psz,prangeA->iFirst,prangeB->iFirst,prangeB->iLast);
Error(ach,"");
}
if (prangeA->iLast > prangeB->iLast) {
sprintf(ach,"%s %ld is not in valid range %ld..%ld",
psz,prangeA->iLast,prangeB->iFirst,prangeB->iLast);
Error(ach,"");
}
}
/*** DumpSummary - Display summary report
*
* Entry
* fFull - TRUE if regions addresses should be displayed.
* g.mp - Filled in with MD BPB from CVF already.
*
* Exit
* Summary written to stdout
*/
void DumpSummary(BOOL fFull)
{
int ireg;
long cbSec; // Count of bytes per sector
printf("\n");
if (g.chDrive != 0)
printf("Drive: %c (mounted from %s)\n",g.chDrive,g.achCVFName);
else
printf("File: %s\n",g.achCVFName);
// Are we just printing the header?
if (!fFull) // Yes
return;
cbSec = g.mp.cbPerSec;
/* 12345678901234567890123456789012345678901234567890123456789012345678901234567890 */
printf("Area Start: Sector Length: cSectors Active: cSectors\n");
printf("-------------- ----------------- ----------------- -----------------\n");
for (ireg=0; ireg<cregMAX; ireg++) {
printf("%-14s %9ld %7ld %9ld %7ld %9ld %7ld\n",
areg[ireg].pszName,
areg[ireg].ibStart, areg[ireg].ibStart/cbSec,
areg[ireg].cbTotal, areg[ireg].cbTotal/cbSec,
areg[ireg].cbActive, (areg[ireg].cbActive+cbSec-1)/cbSec);
// NOTE: Make sure sector count of active area includes last sector
}
}
/*** DumpMDBPB - Dump the MDBPB
*
* Entry
* g.mp - Filled in with MDBPB from CVF already
*
* Exit
* Formatted MDBPB written to stdout
*/
void DumpMDBPB(void)
{
int i;
void *pv;
PrintHeader("MDBPB Structure");
// Print out each member of MDBPB structure
pv = &g.mp;
for (i=0; i<(sizeof(amiMDBPB)/sizeof(MEMBERINFO)); i++) {
if (!(amiMDBPB[i].mt & mtHIDE)) { // Not a hidden member
FormatMember(g.ach,pv,amiMDBPB[i].mt); // Create displayable value
printf("%10s = %s\n",g.ach,amiMDBPB[i].psz); // Display it
}
(char *)pv += cbFromMt[amiMDBPB[i].mt]; // Next structure member
}
}
/*** DumpMDFAT - Dump the MDFAT
*
* Note that MDFAT entries do not correspond directly with DOS FAT
* entries. DBLSPACE.BIN calculates it's MDFAT 'cluster' number by
* dividing the sector number passed from MS-DOS by the sectors per
* cluster. For example, with 8k clusters, FAT cluster 2 (the first
* cluster available for data) might start at sector 192.
* 192 / 16 (sectors per cluster) = 12, the MDFAT entry number. A DOS
* FAT entry number can be converted to it's corresponding MDFAT entry
* number by adding the cluFirstData value to the DOS cluster number.
* For the above example, DOS cluster 2 + cluFirstData (10) = MDFAT
* entry 12. The cluFirstData field contains the number of MDFAT
* entries ('clusters') occupied by the DOS boot record, reserved area,
* and root directory.
*
* Entry
* fh - file handle of CVF
* g.mp - filled in the MDBPB from CVF
* g.rangeMDFAT - range of entries to display
*
* Exit
* Formatted MDFAT written to stdout
*/
void DumpMDFAT(FILEHANDLE fh)
{
char ach[cbOUTPUTLINEMAX];
UINT clPiece;
DWORD cur_mdfat;
DWORD *pMDFAT;
UINT iLine;
DWORD mdfat_entry;
sprintf(ach,"MDFAT entries %ld to %ld",
g.rangeMDFAT.iFirst,g.rangeMDFAT.iLast);
PrintHeader2(ach,
"Flags: A=Allocated, F=Free, C=Compressed, U=Uncompressed");
/* 1234567890123456789012345678901234567890 */
printf("FAT# MDFAT# Flags cUnc cCmp secStart\n");
printf("----- ------ ----- ---- ---- --------\n");
cur_mdfat = g.rangeMDFAT.iFirst;
// Process one piece at a time (limited by our buffer size)
while (cur_mdfat <= g.rangeMDFAT.iLast) {
// Count of MDFAT entries to process
clPiece = (WORD)min(g.rangeMDFAT.iLast-cur_mdfat+1,
sizeof(g.ab)/cbMDFATENTRY);
// Get a piece of the file to print
if (FileRead(fh,
areg[iregMDFAT].ibStart + cbMDFATENTRY*cur_mdfat,
g.ab,
sizeof(g.ab))) {
Error("Read of CVF failed!",NULL);
}
// Display the piece one line at a time
pMDFAT = (DWORD *)g.ab;
for (iLine=0; iLine<clPiece; iLine++) {
mdfat_entry = pMDFAT[iLine]; // fetch the MDFAT entry
printf("%5ld %6ld %5s %2d %2d %8ld\n",
cur_mdfat - g.rangeMDFATValid.iFirst + 2, // FAT number
cur_mdfat, // MDFAT number
szFromMDFAT[3 & (mdfat_entry >> 30)], // Flags
1+ (int) (15 & (mdfat_entry >> 26)), // Count uncomp
1+ (int) (15 & (mdfat_entry >> 22)), // Count comp
mdfat_entry & 0x3fffff); // Sector #
cur_mdfat++;
}
}
}
/*** DumpBitFAT - Dump the BitFAT and/or fragmentation report
*
* Entry
* fh - file handle of CVF
* g.mp - filled in the MDBPB from CVF
* g.rangeBitFAT - range of BitFAT entries to display
* g.fShowBitFAT - TRUE => show BitFAT entries
* g.fShowFragmentation - TRUE => show fragmentation report
*
* Exit
* Formatted BitFAT and/or fragmentation report written to stdout
*/
void DumpBitFAT(FILEHANDLE fh)
{
#define iFREE 0 // Index for counts of free sectors
#define iUSED 1 // Index for counts of used sectors
DWORD aavgRun16[2]; // Average length of runs >= 16 sectors
char ach[cbOUTPUTLINEMAX]; // Line output buffer
char ach1[10]; // Buffer for formatting percentages
char ach2[10]; // Buffer for formatting percentages
DWORD acRun[16][2]; // Count of runs[length][free/used]
DWORD asumRun[2]; // Sum of all sectors [free/used]
DWORD asumRun16[2]; // Sum of sectors in runs >= 16 sec [f/u]
DWORD csecRun;
BOOL fBit;
DWORD i;
int j;
DWORD iRunFirst;
DWORD iRunLast;
if (g.fShowBitFAT) { // Print header for BitFAT
sprintf(ach,"BitFAT for heap sectors %ld to %ld",
g.rangeBitFAT.iFirst,g.rangeBitFAT.iLast);
PrintHeader(ach);
}
if (g.fShowFragmentation) { // Zero counters
for (j=0; j<16; j++) {
acRun[j][iFREE] = 0;
acRun[j][iUSED] = 0;
}
asumRun16[iFREE] = 0;
asumRun16[iUSED] = 0;
}
i = g.rangeBitFAT.iFirst;
iRunFirst = i;
fBit = GetBitFATBit(fh,i);
i++;
while (i <= g.rangeBitFAT.iLast) {
// Find run of bits with same setting
while ( (i <= g.rangeBitFAT.iLast) && (fBit == GetBitFATBit(fh,i)) ) {
i++;
}
// Note run, prepare to look for end of next run
iRunLast = i-1;
csecRun = iRunLast - iRunFirst + 1;
if (g.fShowBitFAT) {
printf("%s %ld to %ld, length %ld\n", fBit ? "USED" : "free",
iRunFirst, iRunLast, csecRun);
}
if (g.fShowFragmentation) { // Accumulate statistics
if (csecRun < 16) { // Run less than 16 sectors
acRun[csecRun-1][fBit] += 1;
}
else { // Run >= 16 sectors
acRun[15][fBit] += 1; // Count run
asumRun16[fBit] += csecRun; // Sum number of sectors
}
}
iRunFirst = i; // New first bit
fBit = !fBit; // Bit is reversed
}
if (g.fShowFragmentation) { // Generate report
PrintHeader("Fragmentation Report");
printf("\n");
printf(" free used\n");
printf("size count count free %% used %%\n");
printf("----- ----- ----- ------- -------\n");
// Compute average length of runs >= 16 sectors long
for (j=0; j<2; j++) {
if (acRun[15][j] > 0)
aavgRun16[j] = asumRun16[j]/acRun[15][j];
else
aavgRun16[j] = 0;
asumRun[j] = asumRun16[j]; // Sum of all runs, by type
}
// Sum up 1..15 sector runs
for (j=0; j<15; j++) {
asumRun[iFREE] += acRun[j][iFREE]*(j+1);
asumRun[iUSED] += acRun[j][iUSED]*(j+1);
}
// Print 1..15 sector runs stats
for (j=0; j<15; j++) {
printf("%5d %5ld %5ld %7s %7s\n",
j+1, acRun[j][iFREE], acRun[j][iUSED],
FormatPercent(ach1,(j+1)*acRun[j][iFREE],asumRun[iFREE]),
FormatPercent(ach2,(j+1)*acRun[j][iUSED],asumRun[iUSED])
);
}
// Print 16+ sector run stats
printf("%5ld %5ld %7s Free runs >= 16 sectors\n",
aavgRun16[iFREE], acRun[15][iFREE],
FormatPercent(ach1,
aavgRun16[iFREE]*acRun[15][iFREE],asumRun[iFREE])
);
printf("%5ld %5ld %7s Used runs >= 16 sectors\n",
aavgRun16[iUSED], acRun[15][iUSED],
FormatPercent(ach2,
aavgRun16[iUSED]*acRun[15][iUSED],asumRun[iUSED])
);
printf("---------------------\n");
printf("Total Free: %9ld\n", asumRun[iFREE]);
printf("Total Used: %9ld\n", asumRun[iUSED]);
printf("---------------------\n");
printf("TOTAL %9ld\n", asumRun[iFREE]+asumRun[iUSED]);
}
}
/*** FormatPercentage - Make PSZ from percentage of two dwords
*
* Entry
* psz - Buffer to receive formatted percentage
* num - Numerator
* den - Denominator
*
* Exit
* psz has percentage num/den, e.g., " 67.03%"
* returns psz, for use by caller
*
* NOTE: If den is zero, then result is " 0.00%".
*/
char *FormatPercent(char *psz,DWORD num,DWORD den)
{
DWORD dwUnit=0; // nnn.00 portion
DWORD dwFrac=0; // 000.nn portion
if (den != 0) {
// NOTE: Num is at most 1024*1024, as that is the maximum number
// of sectors in a CVF sector heap. Since a DWORD holds
// numbers up to 4 billion, we won't overflow here.
dwUnit = (100*num)/den;
// NOTE: To get the .00% amount, we have to reduce the magnitude
// of num (100*100*num would overflow 4 billion in the worst
// case). So we subtract the units amount before we do the
// second multiply by 100.
dwFrac = (100*(num*100 - dwUnit*den))/den;
}
// Now format result
sprintf(psz,"%3ld.%02ld%%",dwUnit,dwFrac);
return psz;
}
/*** GetBitFATBit - Get one bit from the BitFAT
*
* Entry
* fh - file handle of CVF
* i - index of bit to return (using Sector Heap numbering!)
* g.mp - filled in the MDBPB from CVF
*
* Exit
* Returns value of BitFAT[i]
*
* NOTE: g.ab is used as a BitFAT "page" cache (in this case, we
* do no use the standard 2K BitFAT page size), so the caller
* must ensure that g.ab is not modified between calls to
* this routine!
*/
BOOL GetBitFATBit(FILEHANDLE fh, DWORD i)
{
long ib; // Byte index into BitFAT
int ibit; // Bit index into BitFAT word
int iw; // Word index into BitFAT page
int ipage; // Page index into BitFAT
static int ipageCached=-1; // Index of cached BitFAT page in g.ab
WORD *pw=(WORD *)g.ab; // Pointer to BitFAT page
RETCODE rc;
// Adjust index to be zero based
i -= g.rangeBitFATValid.iFirst;
// Get byte index of BitFAT word containing the requested bit
ib = (i>>4)<<1;
// Make sure we have BitFAT page in cache
ipage = (int)(ib / sizeof(g.ab));
if (ipage != ipageCached) { // Read page of bitfat
rc = FileRead(fh,
areg[iregBITFAT].ibStart + ipage*sizeof(g.ab),
g.ab,
sizeof(g.ab)
);
if (rc)
Error("Read failed on CVF BitFAT",NULL);
ipageCached = ipage; // New cached page
}
// Now extract the requested bit
iw = (int)(ib - ipageCached*(long)sizeof(g.ab)) / 2;
ibit = 15 - ((WORD)i % 16); // 0th bit is at bit 15!
//BUG printf("iw=%d, pw[iw]=%04x, ibit=%2d\n",iw,pw[iw],ibit);
if (ibit == 0)
return pw[iw] & 0x01; // Skip the shift
else
return (pw[iw] >> ibit) & 0x01; // Shift and mask
}
/*** DumpCVFSectors - Dump raw sectors from CVF
*
* Entry
* fh - file handle of CVF
* g.mp - filled in the MDBPB from CVF
* g.rangeCVF - range of CVF sectors to dump
*
* Exit
* Selected range from CVF displayed
*/
void DumpCVFSectors(FILEHANDLE fh)
{
char ach[cbOUTPUTLINEMAX];
DWORD iSec;
DWORD offStart;
sprintf(ach,"CVF Sectors %ld to %ld",
g.rangeCVF.iFirst,g.rangeCVF.iLast);
PrintHeader(ach);
for (iSec=g.rangeCVF.iFirst; iSec<=g.rangeCVF.iLast; iSec++) {
printf("\nCVF sector %ld\n",iSec);
offStart = g.mp.cbPerSec*iSec;
DumpHex(fh,offStart,g.mp.cbPerSec);
}
}
/*** DumpDOSBoot - Dump the DOS boot sector
*
* Entry
* fh - file handle of CVF
* g.mp - filled in the MDBPB from CVF
*
* Exit
* Formatted DOS Boot sector written to stdout
*/
void DumpDOSBoot(FILEHANDLE fh)
{
PrintHeader("DOS Boot Sector");
// Don't format the info, just dump hex bytes
DumpHex(fh,areg[iregDOSBOOT].ibStart,256);
}
/*** DumpHeap - Dump the Heap
*
* Entry
* fh - file handle of CVF
* g.mp - filled in the MDBPB from CVF
* g.rangeHeap - range of Sector Heap to dump
*
* Exit
* Selected range from sector heap displayed
*/
void DumpHeap(FILEHANDLE fh)
{
char ach[cbOUTPUTLINEMAX];
DWORD iSec;
DWORD offStart;
sprintf(ach,"Sector Heap entries %ld to %ld",
g.rangeHeap.iFirst,g.rangeHeap.iLast);
PrintHeader(ach);
for (iSec=g.rangeHeap.iFirst; iSec<=g.rangeHeap.iLast; iSec++) {
printf("\nHeap sector %ld\n",iSec);
// MDFAT sector numbers are 1 less than the CVF sector number
offStart = g.mp.cbPerSec*(iSec+1);
DumpHex(fh,offStart,g.mp.cbPerSec);
}
}
/*** DumpDOSFAT - Dump the DOS FAT
*
* Entry
* fh - file handle of CVF
* g.mp - filled in the MDBPB from CVF
*
* Exit
* Formatted DOS FAT sector written to stdout
*/
void DumpDOSFAT(FILEHANDLE fh)
{
PrintHeader("DOS FAT - just a portion of whole table");
DumpHex(fh,areg[iregDOSFAT].ibStart,256);
}
/*** DumpDOSRootDir - Dump the DOS Root directory
*
* Entry
* fh - file handle of CVF
* g.mp - filled in the MDBPB from CVF
*
* Exit
* Formatted DOS root directory written to stdout
*/
void DumpDOSRootDir(FILEHANDLE fh)
{
PrintHeader("DOS Root Directory - just a portion of whole table");
DumpHex(fh,areg[iregDOSROOTDIR].ibStart,256);
}
/*** DumpHex - Dump a portion of a file as Hex bytes
*
* Entry
* fh - File handle of CVF
* iseek - File position
* cb - Count of bytes to dump
*
* Exit-Success
* Formatted information written to stdout
*
* Exit-Failure
* Exits program with error message.
* NOTE: Some output may have been written to stdout
*/
void DumpHex(FILEHANDLE fh, long iseek, long cb)
{
long cbPiece;
RETCODE rc;
int iLine;
int cLine;
DWORD iseekPiece;
BYTE *pb;
iseekPiece = iseek;
// Process one piece at a time (limited by our buffer size)
while (cb > 0) {
cbPiece = (WORD)min(cb,sizeof(g.ab)); // Size of piece to process
cLine = (int)(((long)cbPiece)+cbPERLINE-1)/cbPERLINE; // count of lines
// Get a piece of the file to print
rc = FileRead(fh,iseekPiece,g.ab,sizeof(g.ab));
if (rc)
Error("Read failed on CVF",NULL);
pb = g.ab; // Start of buffer
// Print the piece one line at a time
for (iLine=0; iLine<cLine; iLine++) {
DumpHexLine(iseekPiece,pb); // Print a line
pb += cbPERLINE; // Advance buffer pointer
iseekPiece += cbPERLINE; // Advance seek position
}
cb -= cbPiece; // Reduce amount left to dump
}
}
/*** DumpHexLine - Dump one line of hex output to stdout
*
* Entry
* iseek - File position to report
* pb - Pointer to buffer to dump
*
* Exit
*
* Sample HEX output
*
* 123456789 123456789 123456789 123456789 123456789 123456789 123456789 12345
* 0000 30 31 32 33 34 35 36 37 38 39 00 00 00 00 00 30 0123456789.....0
*
*/
void DumpHexLine(long iseek, BYTE *pb)
{
char ch;
int i;
int ich;
char *pbSave;
#define cchHEXFILEOFFSET 10
#define cchHEXVALUE 3
pbSave = (char *)pb; // Save pointer for ASCII section
// Generate file offset
sprintf(g.ach,"%8lx ",iseek);
ich = cchHEXFILEOFFSET;
// Generate hex values
for (i=0; i<cbPERLINE; ) {
sprintf(&g.ach[ich],"%02x ",pb[i]);
ich += cchHEXVALUE;
i++;
// Check for column break
if ( (i % (cbPERLINE/cCOLUMNS)) == 0) {
g.ach[ich++] = ' '; // Add column separator
}
}
g.ach[ich++] = ' '; // Add space before ASCII section
// Generate ASCII section
for (i=0; i<cbPERLINE; i++) {
if (isprint(pb[i]))
ch = pb[i];
else
ch = chNOTPRINTABLE;
g.ach[ich++] = ch;
}
g.ach[ich++] = '\0'; // Terminate line
printf("%s\n",g.ach); // Print it
}
#endif // ifndef SNAP ------------------------------------------------
/*** FileRead - read data from file at particular location
*
* Entry
* fh - File handle
* iseek - Byte offset from start of file
* 0 = front of file
* -1 = current file position
* pb - Buffer to receive data
* cb - Size of buffer
*
* Exit-Success
* Returns 0
* pb filled in with requested data
*
* Exit-Failure
* Returns non-zero error code
*/
RETCODE FileRead(FILEHANDLE fh, long iseek, void *pb, WORD cb)
{
WORD cbRead;
long iseekNew;
WORD rc;
// Seek to requested location
if (iseek != FR_CURRENT_POS) {
iseekNew = lseek(fh,iseek,SEEK_SET);
if (iseekNew != iseek)
return errno;
}
// Read data
rc = _dos_read(fh,pb,cb,&cbRead);
if (rc != 0) // Call failed
return rc;
if (cbRead != cb) // Did not get enough data
return 1;
return 0; // Success
}
/*** Error - format error message, display it, and exit
*
* Entry
* pszMsg - error message
* pszParm - replacable parm for %s in pszMsg
*
* Exit
* message formatted and displayed
* exit program
*/
void Error(char *pszMsg, char *pszParm)
{
printf("Error: ");
printf(pszMsg,pszParm);
exit(1);
}
#ifndef SNAP // ------------------------------------------------------
/*** FormatMember - Format a value, as indicated by its MEMBERTYPE
*
* Entry
* pch - Buffer to fill in
* pv - Pointer to value
* mt - MEMBERTYPE of value
*
* Exit
* pch filled in with formatted value
*/
void FormatMember(char *pch,void *pv, MEMBERTYPE mt)
{
int i;
WORD w;
WORD w2;
WORD w3;
mt = mt & (~mtHIDE); // Mask off hidden attribute
switch (mt) {
case mtBYTE :
w = (WORD)(*(BYTE *)pv); // WORD from BYTE
sprintf(pch,"%02x",w);
break;
case mtBYTE3 :
w = (WORD)(*((BYTE *)pv+0)); // WORD from BYTE
w2 = (WORD)(*((BYTE *)pv+1)); // WORD from BYTE
w3 = (WORD)(*((BYTE *)pv+2)); // WORD from BYTE
sprintf(pch,"%02x %02x %02x", w, w2, w3);
break;
case mtCHAR :
sprintf(pch,"%c",*(char *)pv);
break;
case mtCHAR8 :
memcpy(g.ach,(char *)pv,8); // Copy string
g.ach[8] = '\0'; // Add null terminator
sprintf(pch,"%s",g.ach);
break;
case mtDWORD :
sprintf(pch,"%08lx",*(DWORD *)pv);
break;
case mtINT1 :
i = (int)(*(char *)pv); // 2 byte int from 1 byte int
sprintf(pch,"%3d",i);
break;
case mtINT2 :
sprintf(pch,"%5d",*(short *)pv);
break;
case mtINT4 :
sprintf(pch,"%10ld",*(long *)pv);
break;
case mtUINT1 :
w = (WORD)(*(BYTE *)pv); // WORD from BYTE
sprintf(pch,"%3u",w);
break;
case mtUINT2 :
sprintf(pch,"%5u",*(WORD *)pv);
break;
case mtUINT4 :
sprintf(pch,"%10lu",*(DWORD *)pv);
break;
case mtWORD :
sprintf(pch,"%04x",*(WORD *)pv);
break;
default :
Error("Unexpected MEMBERTYPE",NULL);
}
}
/*** PrintHeader - print header for a report section
*
* Entry
* psz - section name
*
* Exit
* Section name written to stdout with underlines.
*/
void PrintHeader(char *psz)
{
PrintHeader2(psz,NULL);
}
/*** PrintHeader2 - print two line header for a report section
*
* Entry
* psz1 - first line
* psz2 - second line (ignored if NULL)
*
* Exit
* Lines written to stdout, with underlines after second line.
*/
void PrintHeader2(char *psz1, char *psz2)
{
int cb;
int cb2;
printf("\n%s\n",psz1); // Print name
cb = strlen(psz1);
// Print 2nd line, and update longest line length
if (psz2) {
printf("%s\n",psz2);
cb2 = strlen(psz2);
if (cb2 > cb)
cb = cb2;
}
// Generate and print underline
memset(g.ach,'-',cb);
g.ach[cb] = '\0';
printf("%s\n",g.ach);
}
#endif // ifndef SNAP ------------------------------------------------
/*** ParseArgs - Parse command-line arguments
*
* Entry
* argc - count of arguments
* argv - array of arguments
*
* Exit-Success
* GLOBAL structure (g) fields filled in
*
* Exit-Failure
* Prints message and exits program.
*/
void ParseArgs(int argc, char **argv)
{
char ch;
BOOL fCVFSeen=0; // TRUE if CVF name or drive seen
BOOL fFlagSeen=0; // TRUE if any flags specified
BOOL fFlagSeenOld=0; // Value of fFlagSeen on previous loop iteration
int i;
char *pch;
// Save command line pointers for later
g.argc = argc;
g.argv = argv;
#ifndef SNAP
// Mark all display ranges as defaults, as no values specified, yet
g.rangeBitFAT.iFirst = dwRANGE_DEFAULT;
g.rangeBitFAT.iLast = dwRANGE_DEFAULT;
g.rangeCVF.iFirst = dwRANGE_DEFAULT;
g.rangeCVF.iLast = dwRANGE_DEFAULT;
g.rangeHeap.iFirst = dwRANGE_DEFAULT;
g.rangeHeap.iLast = dwRANGE_DEFAULT;
g.rangeMDFAT.iFirst = dwRANGE_DEFAULT;
g.rangeMDFAT.iLast = dwRANGE_DEFAULT;
#endif
// Parse each argument
for (i=1; i<argc; i++) {
if ( (argv[i][0] == chSWITCH) ||
(argv[i][0] == chSWITCH2) ) {
fFlagSeenOld = fFlagSeen;
fFlagSeen = 1;
pch = &argv[i][1]; // Start with first character
while (*pch) {
ch = (char)toupper((int)*pch);
switch (ch) {
case 'I':
g.fIgnoreSigCheck = 1;
fFlagSeen = fFlagSeenOld; // No display, so ignore as flag
break;
#ifdef SNAP
case 'O': // output file specified
pch++;
if ((*pch == '\0') || (*pch != '=') || (pch[1] == '\0') )
Error("Bad parameter format: %s",argv[i]);
else { // Use specified file
strcpy(g.achOutFileName,pch+1);
pch[1] = '\0'; // Stop parsing this token
// NOTE: We set pch[1], because loop does pch++
// *before* checking for null terminator!
}
break;
#else // SNAP
case 'A': g.fShowAddresses = 1; break;
case 'F': g.fShowDOSFAT = 1; break;
case 'G': g.fShowFragmentation = 1; break;
case 'H': g.fShowHeader = 1; break;
case 'R': g.fShowDOSRootDir = 1; break;
case 'T': g.fShowDOSBoot = 1; break;
case 'B':
g.fShowBitFAT = 1;
ParseRange(&g.rangeBitFAT,&pch);
pch--; // Point to last char, for pch++ below
break;
case 'C':
g.fShowCVFSectors = 1;
ParseRange(&g.rangeCVF,&pch);
pch--; // Point to last char, for pch++ below
break;
case 'M':
g.fShowMDFAT = 1;
ParseRange(&g.rangeMDFAT,&pch);
pch--; // Point to last char, for pch++ below
break;
case 'S':
g.fShowHeap = 1;
ParseRange(&g.rangeHeap,&pch);
pch--; // Point to last char, for pch++ below
break;
case 'V':
g.fShowVerbose = 1; // Remember verbose
// Set all the other flags, except the sector heap
// and CVF flags, since the output for those are
// very, very large.
g.fShowBitFAT = 1;
g.fShowDOSBoot = 1;
g.fShowDOSFAT = 1;
g.fShowDOSRootDir = 1;
g.fShowFragmentation = 1;
g.fShowHeader = 1;
g.fShowMDFAT = 1;
break;
#endif // SNAP
case '?':
ShowSyntax(1);
break;
default:
g.ach[0] = *pch;
g.ach[1] = '\0';
Error("Unknown switch: %s",g.ach);
}
pch++;
}
}
else if (fCVFSeen) {
Error("Too many parameters: %s",argv[i]);
}
else { // Must be the drive letter or CVF name
if (!BuildCVFName(argv[i],g.achCVFName,
sizeof(g.achCVFName),&g.chDrive))
Error("%s",g.achCVFName);
fCVFSeen = 1;
}
}
if (!fCVFSeen) // Use default CVF
if (!BuildCVFName(NULL,g.achCVFName,sizeof(g.achCVFName),&g.chDrive))
Error("%s",g.achCVFName);
#ifndef SNAP
// If no flags specifed, show region addresses
if (!fFlagSeen)
g.fShowAddresses = 1;
#endif
}
/*** ParseRange - parse range specification from command line
*
* Entry
* prange - pointer to RANGE to be filled in
* ppch - pointer to pointer to switch character immediately
* preceding text to be parsed.
*
* Exit-Success
* Returns filled in prange. Note that if only a first value
* is specfied (e.g., 2, as opposed to 2-7), then last is set
* equal to first.
* *ppch is updated to point to next character after range.
*
* Exit-Failure
* Prints error message and exits
*/
void ParseRange(PRANGE prange, char **ppch)
{
BOOL fStartEnd; // TRUE => start-end, FALSE => start+count
char *pchOriginal;
char *pch;
pchOriginal = *ppch;
pch = pchOriginal+1; // Skip flag character
prange->iFirst = ParseDecimal(&pch);
if (prange->iFirst == dwRANGE_DEFAULT) { // No number present
prange->iLast = dwRANGE_DEFAULT; // Last is default, too
*ppch = pch; // Update parsing pointer
return;
}
// Got First number, see if Last number or Count follows
switch (*pch) {
case chRANGE_START_END:
fStartEnd = TRUE;
break;
case chRANGE_START_COUNT:
fStartEnd = FALSE;
break;
default: // No second number, so set last == first
prange->iLast = prange->iFirst;
*ppch = pch; // Update parsing pointer
return;
}
// Get Last number
pch++; // Skip separator
prange->iLast = ParseDecimal(&pch);
if (prange->iLast == dwRANGE_DEFAULT) { // No number present
pchOriginal[1] = '\0'; // Trim off all but switch character
Error("Bad range specified for switch %s",pchOriginal);
}
if (fStartEnd) { // Make sure last >= first
if (prange->iFirst > prange->iLast) {
pchOriginal[1] = '\0'; // Trim off all but switch character
Error("First > Last in range for switch %s",pchOriginal);
}
}
else { // Set last to first+count-1
prange->iLast += (prange->iFirst - 1);
}
// Update parsing pointer
*ppch = pch;
}
/*** ParseDecimal - Parse decimal number
*
* Entry
* ppch - pointer to pointer to potential number
*
* Exit-Success
* Returns a DWORD.
* *ppch adjusted to point immediately following parsed number.
*
* Exit-Failure
* Returns dwRANGE_DEFAULT -- no number was present
*/
DWORD ParseDecimal(char **ppch)
{
DWORD dw=0;
char *pch=*ppch;
if (!isdigit(*pch)) { // No number here
return dwRANGE_DEFAULT;
}
while (isdigit(*pch)) {
dw = dw*10 + (*pch - '0');
pch++;
}
*ppch = pch; // Update command line pointer
return dw; // Return parsed number
}
/*** ShowSyntax - Display command-line syntax
*
*/
void ShowSyntax(BOOL fFull)
{
int i;
int cLines;
if (fFull) { // Show full help
cLines = sizeof(apszSyntax)/sizeof(char *);
for (i=0; i<cLines; i++) {
printf("%s\n",apszSyntax[i]);
}
}
else { // Just show summary line
printf("%s\n",apszSyntax[isynSUMMARY]);
}
exit(0);
}
/*** PrintBanner - print banner for this program
*
*/
void PrintBanner(void)
{
#ifndef SNAP
printf("%s File Dumper - Version %d.%02d\n",szProduct,verMAJOR,verMINOR);
#else
printf("%s Snapper - Version %d.%02d\n",szProduct,verMAJOR,verMINOR);
#endif
}
#ifdef SNAP // -----------------------------------------------------
/*** SnapCVF - 'snap' CVF reserved info to file
*
* Entry
* fhCVF - file handle of open CVF file
*/
void SnapCVF(FILEHANDLE fhCVF)
{
FILEHANDLE fhOut;
int iErr = 0;
UINT iThisLen;
UINT iOutLen;
UINT iLen = 60 * 1024;
long lCpyLen;
char *pCpyBuf;
// Open/create output file
if (_dos_creat(g.achOutFileName, _A_NORMAL, &fhOut) != 0)
Error("Could not create: %s", g.achOutFileName);
// Allocate copy buffer
while ( ((pCpyBuf = malloc(iLen)) == NULL) && (iLen > 4 * 1024) )
iLen -= 4 * 1024;
if (pCpyBuf == NULL)
Error("Insufficient memory for copy buffer",NULL);
// Tell user what we are doing
if (g.chDrive != 0)
sprintf(g.ach,"Drive %c: (mounted from %s)",g.chDrive,g.achCVFName);
else
strcpy(g.ach,g.achCVFName);
printf("Writing snapshot of %s to %s\n",g.ach,g.achOutFileName);
// Copy CVF up to and including root directory to out file
lCpyLen = areg[iregSECTORHEAP].ibStart; // copy to start of sector heap
lseek(fhCVF, 0L, SEEK_SET); // start at the begining
while (lCpyLen > 0) {
iThisLen = (lCpyLen > (long)iLen) ? iLen : (int)lCpyLen;
if (FileRead(fhCVF,FR_CURRENT_POS,pCpyBuf,iThisLen) != 0)
{
iErr = 1;
break;
}
if (_dos_write(fhOut, pCpyBuf, iThisLen, &iOutLen) != 0 ||
iOutLen != iThisLen)
{
iErr = 2;
break;
}
lCpyLen -= iThisLen;
}
// Clean up and exit
free(pCpyBuf);
_dos_close(fhOut);
if (iErr)
Error((iErr==1) ? "Error while reading CVF" :
"Error while writing snap file", NULL);
}
#endif // SNAP --------------------------------------------------------
/*** ComputeRegions - Compute regions of CVF, from MDBPB
*
* NOTE: This is the most interesting part of the program!
*
* Entry
* fh - File handle of CVF
* g.mp - Filled in with MD BPB from CVF already
*
* Exit-Success
* areg filled in.
*
* Exit-Failure
* Prints error message and exits.
*/
void ComputeRegions(FILEHANDLE fh)
{
int ireg; // Index to walk region table
int cbPerSec; // Count of bytes per sector
char csecPerClu; // Count of sectors per cluster
long ccluTotal; // Current total clusters
long ccluTotalMax; // Maximum total clusters
long csecTotal; // Current total sectors
long csecTotalMax; // Maximum total sectors
long seekpos; // File seek position
// Get common values, to make code more readable
cbPerSec = g.mp.cbPerSec; // Count of bytes per sector
csecPerClu = g.mp.csecPerClu; // Count of sectors per cluster
// Get drive size reported to DOS when CVF is mounted
if (g.mp.csecTotalWORD != 0) // Small drive
csecTotal = g.mp.csecTotalWORD;
else // Large drive
csecTotal = g.mp.csecTotalDWORD;
ccluTotal = csecTotal/csecPerClu; // Total number of clusters
// Check CVF signatures
seekpos = (g.mp.csecMDReserved + 1) * (long)cbPerSec;
CheckSignature(fh, seekpos, "first");
if ((g.cbCVF = lseek(fh,0L,SEEK_END)) == -1L)
Error("Could not seek to end of CVF",NULL);
// The 2nd stamp is located at the start of the last complete sector
// in the CVF. If the CVF is exactly a sector multiple, then this
// is indeed the last sector of the file. However, sometimes CVFs are
// not exactly a sector multiple in length, in which case it is the
// next to last sector of the CVF which contains the 2nd stamp.
g.ibCVFStamp2 = (g.cbCVF/cbPerSec - csecRETRACT_STAMP) * cbPerSec;
CheckSignature(fh, g.ibCVFStamp2, "second");
// Get Maximum CVF size information
csecTotalMax = (g.mp.cmbCVFMax * 1024L * 1024L) / cbPerSec;
ccluTotalMax = csecTotalMax / csecPerClu;
// Compute MDBPB region
ireg = iregMDBPB;
areg[ireg].ibStart = 0L; // Always first thing in the CVF
areg[ireg].cbTotal = cbPerSec; // Always consumes one sector
areg[ireg].cbActive = sizeof(MDBPB); // Only MDBPB structure is valid
// Compute BitFAT region
ireg++; // iregBITFAT
areg[ireg].ibStart = areg[ireg-1].ibStart + areg[ireg-1].cbTotal;
// The BitFAT cbTotal should also ==
// (cmbCVFMax * 1024 * 1024) / (8 * cbPerSec)
// (file capicity in sectors / 8 sector bits per byte)
areg[ireg].cbTotal = g.mp.cpageBitFAT * (long)cbPER_BITFAT_PAGE;
// areg[iregBIITFAT].cbActive is computed below, after we know how large
// the sector heap is.
// Compute RESERVED1 region
ireg++; // iregRESERVED1
areg[ireg].ibStart = areg[iregBITFAT].ibStart + areg[iregBITFAT].cbTotal;
areg[ireg].cbTotal = csecRESERVED1 * (long)cbPerSec;
areg[ireg].cbActive = 0; // none in use
// Compute MDFAT region
ireg++; // iregMDFAT
// The MDFAT starts just after the BitFAT so
// secMDFATStart * cbPerSec + cbPerSec (for MDBPB) should ==
// areg[iregBITFAT].ibStart + areg[iregBITFAT].cbTotal
areg[ireg].ibStart = g.mp.secMDFATStart * (long)cbPerSec + (long)cbPerSec;
// The MDFAT size depends on the maximum number of clusters that the CVF
// could hold, but we will compute it instead by inference from the
// other information we have, so we'll calculate the MDFAT size as
// MDReserved - BitFAT size - MDBPB size - other reserved sizes
areg[ireg].cbTotal = (g.mp.csecMDReserved * (long)cbPerSec) -
areg[iregMDBPB].cbTotal - // MDBPB size
areg[iregBITFAT].cbTotal - // BitFAT size
areg[iregRESERVED1].cbTotal - // RESERVED1 size
(csecRESERVED2 * (long)cbPerSec); // RESERVED2 size
areg[ireg].cbActive = ccluTotal * cbMDFATENTRY;
// Compute RESERVED2 region
ireg++; // iregRESERVED2
areg[ireg].ibStart = areg[iregMDFAT].ibStart + areg[iregMDFAT].cbTotal;
areg[ireg].cbTotal = csecRESERVED2 * (long)cbPerSec;
areg[ireg].cbActive = 0; // none in use
// Compute BOOT region
ireg++; // iregDOSBOOT
areg[ireg].ibStart = g.mp.csecMDReserved * (long)cbPerSec;
areg[ireg].cbTotal = cbPerSec;
areg[ireg].cbActive = cbPerSec;
// Compute RESERVED3 region
ireg++; // iregRESERVED3
areg[ireg].ibStart = areg[iregDOSBOOT].ibStart
+ areg[iregDOSBOOT].cbTotal;
areg[ireg].cbTotal = (g.mp.csecReserved - 1) * (long)cbPerSec;
areg[ireg].cbActive = 0;
// Compute DOSFAT region
ireg++; // iregDOSFAT
areg[ireg].ibStart = areg[iregDOSBOOT].ibStart +
(g.mp.csecReserved * (long)cbPerSec);
areg[ireg].cbTotal = g.mp.csecFAT * (long)cbPerSec;
areg[ireg].cbActive = g.mp.f12BitFAT ?
((ccluTotal * 3)/2) : // 12-bit FAT
(ccluTotal * 2); // 16-bit FAT
// Compute ROOTDIR region
ireg++; // iregDOSROOTDIR
areg[ireg].ibStart = (g.mp.secRootDirStart + g.mp.csecMDReserved)
* (long)cbPerSec;
areg[ireg].cbTotal = g.mp.cRootDirEntries * cbDIR_ENT;
areg[ireg].cbActive = areg[ireg].cbTotal;
// Compute RESERVED4 region
ireg++; // iregRESERVED4
areg[ireg].ibStart = areg[iregDOSROOTDIR].ibStart
+ areg[iregDOSROOTDIR].cbTotal;
areg[ireg].cbTotal = csecRESERVED4 * (long)cbPerSec;
areg[ireg].cbActive = 0;
// Compute SECTORHEAP region
ireg++; // iregSECTORHEAP
areg[ireg].ibStart = areg[iregRESERVED4].ibStart
+ areg[iregRESERVED4].cbTotal;
// Total and Active SECTORHEAP sizes are the same -- unlike other
// regions, the SECTORHEAP is not preallocated for the max capacity.
// The SECTORHEAP is followed by the 2nd MD STAMP, which occupies
// the last <2 sectors of the CVF. Since we already did the
// RETRACT_STAMP computation above, all we have to do is subtract
// the start of the sector heap from the start of the 2nd stamp.
areg[ireg].cbTotal = g.ibCVFStamp2 - areg[ireg].ibStart;
areg[ireg].cbActive = areg[ireg].cbTotal;
// Now we can compute the active region of the BitFAT. There is
// one bit in the BitFAT for every sector in the sector heap, and
// we round up to the nearest byte.
areg[iregBITFAT].cbActive = (areg[iregSECTORHEAP].cbTotal/cbPerSec + 7) / 8;
#ifndef SNAP
// Compute valid ranges
g.rangeCVFValid.iFirst = 0;
g.rangeCVFValid.iLast = (g.cbCVF+cbPerSec-1)/cbPerSec;
// Heap sector numbers are one less than their CVF position
g.rangeHeapValid.iFirst = areg[iregSECTORHEAP].ibStart/cbPerSec - 1;
g.rangeHeapValid.iLast = g.rangeHeapValid.iFirst
+ areg[iregSECTORHEAP].cbTotal/cbPerSec
- 1;
g.rangeMDFATValid.iFirst = 2 + g.mp.cluFirstData;
g.rangeMDFATValid.iLast = g.rangeMDFATValid.iFirst
+ areg[iregMDFAT].cbActive/cbMDFATENTRY
- 1;
g.rangeBitFATValid.iFirst = g.rangeHeapValid.iFirst;
g.rangeBitFATValid.iLast = g.rangeHeapValid.iLast;
#endif
}
/*** CheckSignature - Check a CVF signature
*
* Entry
* fh - File handle of CVF
* seekpos - Position to check
* pszName - Signature name
*
* Exit-Success
* Returns.
*
* Exit-Success
* Prints error;
* if g.fIgnoreSigCheck is set, returns
* else Exits program
*/
void CheckSignature(FILEHANDLE fh, long seekpos, char *pszName)
{
char achStamp[cbDS_STAMP]; // Buffer to read stamp
if (FileRead(fh,seekpos,achStamp,sizeof(achStamp)) != 0) {
sprintf(g.ach,"Could not read %s signature: %s",pszName,g.achCVFName);
goto Error;
}
if ( (strcmp(achStamp,szDS_STAMP1) != 0) &&
(strcmp(achStamp,szDS_STAMP2) != 0) ) {
sprintf(g.ach,"Bad %s signature: %s",pszName,g.achCVFName);
goto Error;
}
return; // Signature is okay
Error: // Signature is bad
if (g.fIgnoreSigCheck)
printf("%s\n",g.ach);
else
Error("%s",g.ach);
}
/*** BuildCVFName - Parse CVF abbreviations into full file name
*
* Entry
* psz - possible CVF abbreviation
* <empty> => Full path of CVF for current drive
* d: => Full path of CVF for specified drive
* NOTE: If drive is not a DS drive, assume it is
* a host drive, and take 000.
* [path]file => Assume it is a CVF file name
*
* ach - buffer to receive full name
* cb - length of ach (should be >80 bytes, for error message!)
* pchDrive - receives drive letter of DS drive (if specified)
*
* Exit-Success
* returns TRUE
* ach filled in with fully-qualified CVF path;
*
* Exit-Failure
* returns FALSE
* ach filled in with error message
*/
BOOL BuildCVFName(char *psz, char ach[], int cb, char *pchDrive)
{
SEQ seq;
int dr;
BYTE drHost;
BOOL fSwapped;
if ( (psz == NULL) || (*psz == '\0') ) { // <empty>
_dos_getdrive(&dr); // Get current drive number (1-based)
dr--; // Make drive zero-based
}
else if (IsDriveSpec(psz)) { // Could be just drive letter
dr = (char)toupper((int)psz[0])-'A'; // Zero-based drive number
}
else { // Allow any file name
strcpy(ach,psz);
*pchDrive = 0; // Drive not specified
return TRUE;
}
// Build CVF path from host drive and sequence number
if (IsDoubleSpaceDrive((BYTE)dr,&fSwapped,&drHost,&seq)) {
*pchDrive = (char)(dr+'A'); // Mounted drive letter
sprintf(ach,"%c:\\%s.%03d",(int)drHost+'A',szCVF_ROOT,seq);
return TRUE;
}
else {
sprintf(ach,"Not a DoubleSpace drive: %c",dr+'A');
return FALSE;
}
}
/*** IsDriveSpec - checks if parameter is a valid drive specifier
*
* Entry
* psz - string to check; valid forms are:
* "a" or "a:", where a is in [a..zA..Z]
*
* Exit-Success
* Returns TRUE.
*
* Exit-Failure
* Returns FALSE.
*/
BOOL IsDriveSpec(char *psz)
{
int cb;
if (psz == NULL)
return FALSE;
cb = strlen(psz);
if (cb > 2)
return FALSE;
if (!isalpha(*psz))
return FALSE;
if ( (cb == 2) && (psz[1] != ':') )
return FALSE;
return TRUE;
}